//use concurrent stack now!
//no need follow strategy anymore!

//you should define SIZEX/SIZEY/SIZEZ/BLKX/BLKY/ITER
//actually 2d-partitioning in this program 

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <assert.h>

#include <sys/time.h>
#include "3d7p_aceMesh.h"
#include "aceMesh_runtime_c.h"
//#include "task.h"
double A[2][SIZEX][SIZEY][SIZEZ]; //attribute ((aligned(64)));
//for test
double test[2][SIZEX][SIZEY][SIZEZ];

extern void  slave_stencil_core(targs* args) ;
//arg
#define alpha_d 0.0876
#define beta_d  0.0765
#define gamma_d 0.0654
#define delta_d 0.0543
#define FREQ 1450000000.0

#ifdef _PROFILING
int hit[64]={0};
unsigned long slave_dma_count[64]={0};
unsigned long total_dma_count=0;
unsigned long slave_comp_count[64]={0};
unsigned long total_comp_count=0;
unsigned long slave_trans_count[64]={0};
unsigned long total_trans_count=0;
//unsigned long slave_dma_hit_count[64]={0};
#endif
/*unsigned long rpcc()
{
  unsigned long time;
  asm("rtc %0":"=r"(time):);
  return time;
}*/

int local_affinity=0;
//
//void my_neighbors(void** neighbor_addrs,int* num_neigh, targs* args)
//{
//   int starti=args->starti;
//    int startj=args->startj;
//    void* src_addr=args->src_addr;
//  
//    *num_neigh=0;
//    if(starti - BLKX>=0)
//        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr - BLKX * SIZEY * SIZEZ * 8);
//    if(starti + BLKX < SIZEX)
//        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr + BLKX * SIZEY * SIZEZ * 8);
//    if(startj - BLKY >= 0)
//        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr - BLKY * SIZEZ * 8);
//    if(startj + BLKY < SIZEY)
//        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr + BLKY * SIZEZ* 8);
//#ifdef _3d27p
//    if(starti-BLKX>=0&&startj-BLKY>=0)
//        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr - BLKX * SIZEY * SIZEZ * 8-BLKY*SIZEZ*8);
//    if(starti + BLKX < SIZEX&&startj-BLKY>=0)
//        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr + BLKX * SIZEY * SIZEZ * 8-BLKY * SIZEZ * 8);
//    if(starti -BLKX>=0&&startj + BLKY < SIZEY)
//        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr - BLKX * SIZEY * SIZEZ * 8+ BLKY * SIZEZ* 8);
//    if(starti  + BLKX < SIZEX&&startj + BLKY < SIZEY)
//        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr + BLKX * SIZEY * SIZEZ * 8+BLKY * SIZEZ* 8);
//#endif
//}


int Do3d7p(const int iter, const int thread_num)
{
#ifdef RESET_AFFI
    int my_local_affinity_id=0;
    int ntilex=((SIZEX-2)/BLKX)/thread_num;
#endif
    targs each;
    int itr,i,j;
    int local_itr=0;
#ifdef REUSE_GRAPH
    int inner_itr;
    for(itr = 0; itr < iter; itr+=DIST)
    {
      if(!acemesh_dag_start(1))
      {
        for(inner_itr=0;inner_itr<DIST;inner_itr++)
        {
#else
    for(itr = 0; itr < iter; ++itr)
    {
#endif
        acemesh_begin_split_task("3d7p iter");
        for(i = 1; i < SIZEX -1; i += BLKX)
        {
            int endi = (i + BLKX > SIZEX -1 ) ? SIZEX - 1 : i + BLKX;
            for(j = 1; j < SIZEY -1; j += BLKY)
            {
                int endj = (j + BLKY > SIZEY -1 ) ? SIZEY - 1 : j + BLKY;
                each.starti=i;
                each.endi=endi;
                each.startj=j;
                each.endj=endj;
                each.k_N=SIZEZ;
#ifdef REUSE_GRAPH
                local_itr=inner_itr;
#else
                local_itr=itr;
#endif
                each.iter=local_itr;
                //each.src_addr=&A[(local_itr+1)%2][i][j][0];
                acemesh_push_rlist(1, &A[0][i][j][0],NORMAL);
                acemesh_push_wrlist(1, &A[1][i][j][0], NORMAL);
                acemesh_task_generator((TASK_FUNCPTR)slave_stencil_core,
                      (void*)(&each), sizeof(targs));
                acemesh_task_set_type(STENCIL_TASK);
#ifdef RESET_AFFI
                //my_local_affinity_id=(local_affinity++)%thread_num;
                //my_local_affinity_id=(local_affinity)%thread_num;
                my_local_affinity_id=local_affinity/ntilex;
                //printf("%d;",my_local_affinity_id);
                acemesh_task_set_affinity(my_local_affinity_id);
#endif
            }
#ifdef RESET_AFFI
            local_affinity++;
#endif
        }
        acemesh_end_split_task();
//        acemesh_begin_split_task("reduction");
//        acemesh_task_generator((TAKS_FUNCPTR)slave_reduction_core,NULL,0);
//        acemesh_task_set_type(BARRIER_TASK);
//        acemesh_end_split_task();
#ifdef REUSE_GRAPH
        }
      }
      acemesh_spawn_and_wait(1);
#endif
      //acemesh_spawn_and_wait(1);
#ifdef RESET_AFFI
        local_affinity=0;
#endif
    }
#ifndef REUSE_GRAPH
    acemesh_spawn_and_wait(1);
#endif
    return 0;
}



void init()
{
    int i,j,k;
    for( i = 0; i < SIZEX; i++)
     for( j = 0; j < SIZEY; j++)
      for( k = 0; k < SIZEZ; k++){
        A[1][i][j][k] = (double)(i*2.5 + j*3.3 + k*0.5 ) /3.0; 
        A[0][i][j][k]=A[1][i][j][k];
     }
}
int check(const int iter)
{
   int i,j,k;
   int itr;
   int ii,jj,kk;
   printf("begin check\n");
    for(i = 0; i < SIZEX; i++)
     for( j = 0; j < SIZEY; j++)
      for( k = 0; k < SIZEZ; k++){
         test[1][i][j][k] = (double)(i*2.5 + j*3.3 + k*0.5 ) /3.0; 
         test[0][i][j][k] =  test[1][i][j][k];
      }
       
    for(itr = 0; itr < iter; ++itr){
     for(i = 1; i < SIZEX - 1; i++)
      for( j = 1; j < SIZEY - 1; j++)
       for( k = 1; k < SIZEZ - 1; k++)
          {
//      if((i==1)&&j==1&&k==1)
  //        printf("master:iter:%d,%f,%f,%f,%f,%f,%f,%f\n",itr,test[(itr+1)%2][i][j][k],test[(itr+1)%2][i-1][j][k],\
                 test[(itr+1)%2][i+1][j][k] , test[(itr+1)%2][i][j-1][k],test[(itr+1)%2][i][j+1][k],\
                  test[(itr+1)%2][i][j][k-1],test[(itr+1)%2][i][j][k+1]);
#ifndef _3d27p
            test[itr%2][i][j][k] = alpha_d * test[(itr+1)%2][i][j][k] + \
                        beta_d * (test[(itr+1)%2][i-1][j][k] + test[(itr+1)%2][i+1][j][k] +\
                        test[(itr+1)%2][i][j-1][k] + test[(itr+1)%2][i][j+1][k] +\
                        test[(itr+1)%2][i][j][k-1] + test[(itr+1)%2][i][j][k+1] ); 
#else
            test[itr%2][i][j][k] = alpha_d * test[(itr+1)%2][i][j][k] + \
                        beta_d * (test[(itr+1)%2][i-1][j][k] + test[(itr+1)%2][i+1][j][k] +\
                        test[(itr+1)%2][i][j-1][k] + test[(itr+1)%2][i][j+1][k] +\
                        test[(itr+1)%2][i][j][k-1] + test[(itr+1)%2][i][j][k+1] )+\
                        gamma_d*(test[(itr+1)%2][i][j-1][k-1] + test[(itr+1)%2][i][j-1][k+1]+\
                        test[(itr+1)%2][i][j+1][k-1] + test[(itr+1)%2][i][j+1][k+1]+\
                        test[(itr+1)%2][i-1][j][k-1] + test[(itr+1)%2][i-1][j][k+1] +\
                        test[(itr+1)%2][i+1][j][k-1] + test[(itr+1)%2][i+1][j][k+1] +\
                        test[(itr+1)%2][i-1][j-1][k] + test[(itr+1)%2][i-1][j+1][k]+\
                        test[(itr+1)%2][i+1][j-1][k] + test[(itr+1)%2][i+1][j+1][k])+\
                        delta_d*(test[(itr+1)%2][i-1][j-1][k-1] + test[(itr+1)%2][i-1][j-1][k+1] +\
                        test[(itr+1)%2][i-1][j+1][k-1] + test[(itr+1)%2][i-1][j+1][k+1] +\
                        test[(itr+1)%2][i+1][j-1][k-1] + test[(itr+1)%2][i+1][j-1][k+1] +\
                        test[(itr+1)%2][i+1][j+1][k-1] + test[(itr+1)%2][i+1][j+1][k+1] ); 
#endif
}
      }
       
    for(ii = 1; ii < SIZEX - 1; ii++)
      for(jj = 1; jj < SIZEY - 1; jj++)
       for(kk = 1; kk < SIZEZ - 1; kk++){
          if( fabs(test[(iter+1)%2][ii][jj][kk]  - A[(iter+1)%2][ii][jj][kk]) > 1e-16 ){
             printf("i: %d   j:  %d k: %d\n",ii,jj,kk);    
             printf("test: %20.20f",test[(iter+1)%2][ii][jj][kk]);
             printf("but A: %20.20f",A[(iter+1)%2][ii][jj][kk]);
             printf("they are different!\n");               
             return -1;
         }
      }
  printf("correct\n");
  return 0;
}



int main(int argc, char** argv)
{
    int ITER,num_threads;
    int i,j,k;
    struct timeval start;
    struct timeval end;
    double total_time;      
    int total_hit=0; 
    unsigned long total_dma_hit_count;
    int p = 1;
    unsigned long st,et;
    //penv_slave0_cycle_init();
    printf("********************\n");
    printf("*     begin        *\n");
    printf("********************\n");
    fflush(stdout);
    ITER = 30;
    num_threads = 1;
    if(argc > 1)
        num_threads = atoi(argv[1]);
    if(argc > 2)
        ITER = atoi(argv[2]);
    if(((SIZEX-2)%BLKX) != 0 || ((SIZEY-2)%BLKY) != 0 || ((SIZEZ-2)%BLKZ) != 0)
    {   
      printf("(SIZEX-2)%BLKX) and ((SIZEY-2)%BLKY) and ((SIZEZ-2)%BLKZ) must == 0 \n");
      assert(0);
    }
    init();
    gettimeofday(&start,NULL);
    //while(p);
    acemesh_runtime_init(num_threads);
    st=rpcc();
    Do3d7p(ITER, num_threads);
    et=rpcc();
    acemesh_runtime_shutdown();  
#ifdef _PROFILING
    for(i=0;i<63;++i)
    {
       total_comp_count+=(slave_comp_count[i]);
       total_dma_count+=(slave_dma_count[i]);
       //total_dma_hit_count+=slave_dma_hit_count[i];
      //total_hit+=hit[i];
       total_trans_count+=slave_trans_count[i];
    }
    printf("total_count=%lf\ntotal_comp_count=%lf\ntotal_dma_count=%lf\ntotal_trans_count=%lf\n",
           (et-st)/FREQ,total_comp_count/FREQ,total_dma_count/FREQ,total_trans_count/FREQ);
#endif
#ifdef CHECK
    if(!check(ITER))
    {
        printf("check ok\n");
    }
    else 
    {
        printf("check_error\n");
        exit(1);
    }
#endif
    return 0;
}
